--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════#
--║     RAIL MOBJ FUNCTIONS    ║ Functions usually called by mobjs on rails to get info or do actions.
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════#
local RAIL = GS_RAILS if RAIL.blockload return end
local RAILTYPE = GS_RAILS_TYPES
local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end

--Flingjump physics not dependent on an existing rail.
RAIL.flingjump = function(s, GS, GRINDING, HANG)
	if GS==nil or GS.LastGrindZ==nil or not (s.health) or (s.eflags & MFE_SPRUNG)
		return --
	end
	local TMOMZ = s.z-GS.LastGrindZ --Add slope momentum to rail switching too.	
	if not (TMOMZ) 
		if GS.LastGrindZ2==nil
			return
		else
			TMOMZ = s.z-GS.LastGrindZ2
		end
	elseif GS.LastGrindZ2!=nil
		local TMOMZ2 = s.z-GS.LastGrindZ2
		if (TMOMZ < 0) and (TMOMZ2 < TMOMZ)
		or (TMOMZ > 0) and (TMOMZ2 > TMOMZ)
			TMOMZ = ($+TMOMZ2)/2 --Take average!
		end
	end
	local p = s.player
	if p and P_PlayerInPain(p) and not p.powers[pw_carry] and not (p.powers[pw_justlaunched]==2)
		return --
	end	
	local rail = GS.myrail
	if not (rail and type(rail)=="userdata" and rail.valid)
		rail = GS.myrail or GS.LastRail
	end
	if HANG==nil 
		HANG = false
		if p and (RAIL.GrindState(s, "hang"))
			HANG = true
		elseif rail
			if not (s.eflags & MFE_VERTICALFLIP) and rail.GShangrail
			or (s.eflags & MFE_VERTICALFLIP) and not rail.GShangrail
				HANG = true
			end
		end
	end
	local SPEED = abs(s.momx)+abs(s.momy)
	if (SPEED > s.scale)
		if GRINDING==nil GRINDING = GS.grinding or 0 end
		local DIV = min(50, max(-20, TMOMZ/s.scale))
		if (GRINDING < 14)
			if (DIV > 0) --If you haven't been grinding long, don't retain as much speed from upwards slopejumps.
				DIV = $+(14-GRINDING)
			elseif (DIV < 0) --On the other hand, downward slopes can be beneficial in a way.
				DIV = $-(9+(GRINDING/2))
			end
		end
		if (DIV) --Horizontal slopejump momentum adjustments.
		and not ((DIV < 0) and (SPEED > (80-DIV)*s.scale))
			if HANG==true and not (GS.railswitching)
				s.momx,s.momy = FixedDiv(FixedMul($, 70), 70+DIV), FixedDiv(FixedMul($, 70), 70+DIV)
			else
				s.momx,s.momy = FixedDiv(FixedMul($, 111), 111+DIV), FixedDiv(FixedMul($, 111), 111+DIV)
			end
		end
	end
	if (TMOMZ > 10*s.scale) --Diminishing returns.
		if (TMOMZ > 30*s.scale)
			TMOMZ = max(30*s.scale, $*3/4)
		elseif (TMOMZ > 20*s.scale)
			TMOMZ = max(20*s.scale, $*8/9)
		else
			TMOMZ = max(10*s.scale, $*11/12)						
		end
	elseif (TMOMZ < -6*s.scale)
		if (TMOMZ < -14*s.scale)
			TMOMZ = min(-14*s.scale, $*5/6)
		else
			TMOMZ = min(-6*s.scale, $*9/10)
		end
	end
	TMOMZ = min(38*s.scale, max($, -30*s.scale)) --Absolute limits.
	local ZFLIP = P_MobjFlip(s) or 1 
	s.pmomz = 0
	s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ)
	s.momz = $+(TMOMZ*2/3*ZFLIP) --Vertical slopejump momentum!
	local VERTJUMP = false
	if (s.momz*ZFLIP < 3*s.scale) and not GS.railswitching 
		if GS.MyRailStats and GS.MyRailStats.VERTRAIL
		or rail and (rail.GSLoopType or rail.GSVertRail)
			VERTJUMP = true
		else
			s.momz = 3*s.scale*ZFLIP --Hey, go up at least a LITTLE bit...
		end
	elseif (s.eflags & MFE_VERTICALFLIP) --and not (VERTJUMP) --Weird fix
		s.momz = $*10/17
	end
	if p and ((GRINDING or 0) < 4) 
	and not (p.pflags & (PF_STARTJUMP|PF_JUMPED)) and (VERTJUMP!=true) --We LITERALLY just started?
		s.momz = $/4
	end
end

--Hey you! Get off-a that rail!
RAIL.ExitRail = function(s, convertmomentum, sameXY, rail)
	local GS = s.GSgrind if GS==nil RAIL.SetupMobjRailVars(s) GS = s.GSgrind end
	local p = s.player
	if not (rail and type(rail)=="userdata" and rail.valid)
		rail = GS.myrail or GS.LastRail
	end
	local JUMPRAMP = 0
	local SKIN,STATS,ROLLSTART = nil
	local RSTATS = GS.MyRailStats	
	local LOOP = RAIL.LearnLoopType(rail)
	local JUMPFLAGS,LOOPFLAGS,GRINDFLAGS = 0,0,0
	local CONVEYOR = 0 if (rail and rail.GSconveyor) CONVEYOR = rail.GSconveyor<<16 end
	local CONV = CONVEYOR
	local LTAB = {init = 0, timer = 10, lastANG = s.angle, lastspeed = GS.railspeed, sameXY = sameXY}

	if rail and rail.valid
		LTAB.rail = rail.GSrailnum
		JUMPFLAGS,LOOPFLAGS,GRINDFLAGS = rail.GSjumpflags or 0,rail.GSloopflags or 0,rail.GSgrindflags or 0	
		if (GRINDFLAGS & 524288) and (CONVEYOR)
			GS.railspeed = CONVEYOR
		end
		if rail.GSrailspeed and GS.railspeed --Adjust at the last second for more sensible physics.
			GS.railspeed = FixedDiv(FixedMul($, max(5, rail.GSrailspeed)), 100)
		end
		if not (GS.railspeed+CONV < 0) --What was our next rail?
			if rail.GSnextrail and rail.GSnextrail.GSrailnum!=nil
				LTAB.nextrail = rail.GSnextrail.GSrailnum
			end
		elseif rail.GSprevrail and rail.GSprevrail.GSrailnum!=nil
			LTAB.nextrail = rail.GSprevrail.GSrailnum
		end
		if (JUMPFLAGS & 16777216) and (sameXY=="jump" or sameXY=="sidehop") and rail.spawnpoint 
			if rail.spawnpoint.tag==0
				rail.spawnpoint.tag = 42763 --Hit a random-feeling number and pray it's not needed.
				RAIL.error("\x85"+"ERROR:\x80 rail[\x82"+s.GSrailnum+"\x85] has a linedef trigger flag"+ 
				", yet no tag assigned!"+"\n"+"Spawned\x81 debug TOAD\x85 on location.")	
			elseif type(rail.spawnpoint.tag)=="number"
				P_LinedefExecute(rail.spawnpoint.tag, s, (s.subsector) and s.subsector.sector or nil)
				if consoleplayer==displayplayer and consoleplayer and consoleplayer.GSRO and consoleplayer.GSRO.devmode
					CONS_Printf(consoleplayer, "Activating\x84 Linedef Trigger"+"\x80"+"[tag:\x88"+rail.spawnpoint.tag+"\x80"+
					"] - Called by Railnum[\x82"+rail.GSrailnum+"\x80"+"]")
				end
			end
		end		
	end
	if p
		LTAB.lastANG = p.drawangle	
		if LOOP and (LOOPFLAGS & (262144|524288))
			s.frame = $ & ~FF_VERTICALFLIP
		end
		if (rail and rail.GSnorolling)
			GS.spinstate = nil
		end
		GS.speedtrail,GS.FreeAdd,GS.speedtrail,GS.curanim,GS.forcesprite,GS.homingANG = nil --Bye bye.
		s.spritexoffset,s.spriteyoffset = 0,0
		s.mirrored = false
		if GS.stretch GS.stretch.timer = 1 end
		
		if not (sameXY=="jump" or sameXY=="sidehop")
			if RSTATS and ((RSTATS.falling or 0)>15)		
				local SPRITE2 = (s.sprite2 & ~SS2)
				RSTATS.falling = 0
				if not GS.railtilt 
					GS.railtilt = (p.cmd.sidemove < 0) and -770 or 770
				end
				RAIL.FAILROLL(p,s,GS, true)
				if LOOP and not (LOOP.walljump) and (LOOP.endwithspin!=false)
					if (GS.spinstate) or (p.cmd.buttons & BT_SPIN) and p.charability2==CA2_SPINDASH or SPRITE2==SPR2_ROLL
					or LOOP.endwithspin=="force" or LOOP.endwithspin=="always"
						if not (rail and (rail.GSnorolling))
							GS.failroll,GS.failrollang,s.rollangle,GS.FuckingROLL = 0,0,0,9  --Brute force time.
							ROLLSTART = true --Roll off instead.
						end
					end
				end
				if (ROLLSTART!=true) return end --Fell off. Good job, idiot.
			end
		end
		SKIN = GS_RAILS_SKINS[s.skin]
		STATS = SKIN and SKIN["S_SKIN"]
		if SKIN and SKIN["PreExit"] and (SKIN["PreExit"](p,s,GS,rail))==true
			return --
		end
		if s.movefactor==0 s.movefactor = FRACUNIT end
		s.friction = FRACUNIT
		if GS.dashmode
			if GS.normalspeed
				p.normalspeed = max(skins[s.skin].normalspeed, GS.normalspeed)
			end
			p.dashmode = GS.dashmode or 0
		end
		GS.animcall,GS.FrontiersLeap,GS.JumpRamp,GS.airdrag,GS.dashmode,GS.normalspeed,GS.flipanim,GS.flip,GS.flipanimCD = nil
		p.secondjump,p.fly1 = 0,0
		
		if sameXY=="jump"
			if (JUMPFLAGS & 524288)
				LTAB.doflip = true	
			end
			if rail and rail.GSjumprail
				local JUMP = rail.GSjumprail or 0
				if JUMP==2 --Frontiers leap.
					GS.FrontiersLeap = 12
					JUMPRAMP = 2
					if (GRINDFLAGS & 524288) --Zero momentum flag + Frontiers Leap = jumpboost!
						LTAB.dojumpboost = (GS.TWOD==true) and "2D" or "3D"
					end
				else
					local REQSPEED = abs(GS.railspeed or 0)
					if (REQSPEED > 39*s.scale) or (LOOP and LOOP.walljump) or GS.ropesnap==rail --SA2 jump-ramp"
						if JUMP==1 and (GS.railspeed >= 0)
						or JUMP==-1 and (GS.railspeed < 0)
						or GS.ropesnap==rail 
							GS.JumpRamp = {timer = 61, STARTANG = p.drawangle, ANG = p.drawangle, rollangle = -ANG2*3, init = 0, ropesnap = GS.ropesnap or nil} 
							JUMPRAMP = JUMP
							if GS.JumpRamp.ropesnap // or p.solcam and (mapheaderinfo[gamemap].solcam=="on")
								if p.awayviewmobj==GS.railcam p.awayviewmobj = nil end
								GS.docam = 0
								GS.nocam = max(GS.nocam or 0, 16)
							end
						end
					end
				end
			end
		end
		if GS.railcam and GS.railcam.valid or (p.awayviewaiming and p.awayviewmobj)
			GS.aimingreset = 12
		end
		if not p.cmd.forwardmove and not p.cmd.sidemove
			if (GS.TWOD==true) or (s.flags2 & MF2_TWOD)
				p.cmd.sidemove = (s.momx <= 0) and -1 or 1 
			elseif (LOOPFLAGS & (262144|524288))
				p.cmd.sidemove = (s.momy <= 0) and -1 or 1
				if LOOP and LOOP.walljump or GS.ropesnap
					p.cmd.forwardmove = 0
				end
			else
				p.cmd.forwardmove = 1 --Prevents stupid shenanigans.
			end
		end
		p.homing = 0
	end
	local TYPE = RAILTYPE[RAIL.LearnRailType(rail or s)]	
	local SLOPEFLING = {TYPE.grindstartSFX or sfx_gsral1, TYPE.grindSFX or sfx_gsral3, 
	TYPE.faststartSFX  or sfx_gsral2, TYPE.fastgrindSFX  or sfx_gsral4, sfx_gsral8}
	
	for _,i in pairs(SLOPEFLING) if i S_StopSoundByID(s, i) end end --Stop some sounds.
	SLOPEFLING = 0
	
	local ZFLIP = P_MobjFlip(s or rail) or 1	
	local VERTRAIL = ((RSTATS) and RSTATS.VERTRAIL or (rail) and rail.GSVertRail) or 0
	local RAILANG = RAIL.ANG(rail)
	local ZSPEED = nil
	
	if not (VERTRAIL)	
		if ZFLIP == -1
			local SCALE = (abs(s.z-s.ceilingz) < 12*s.scale) and s.scale or s.scale/3
			s.z = $-(((sameXY=="sidehop") and 6 or 5)*SCALE)
		else
			local SCALE = (abs(s.z-s.floorz) < 12*s.scale) and s.scale or s.scale/3
			s.z = $+ (((sameXY=="sidehop") and 6 or 5)*SCALE)
		end
		if rail and not GS.leftrail and not (JUMPRAMP and (JUMPRAMP!=2)) --Shouldn't happen, buuut just incase...
			s.momz = -((P_GetMobjGravity(s) or 0)*9/8)
			s.pmomz = 0
		end	
		
	else --Special vertical rail?
		if rail.GSnextrail and (rail.GSnextrail.z < rail.z)
			RAILANG = $+ANGLE_180
		end
		if p
			if RSTATS 
				ZSPEED = RSTATS.ZSPEED
			end
			if GS.railcam and GS.railcam.valid --Keep your camera for a bit.
				GS.docam = max(GS.docam or 0, 12)
			end
			for i = 1,3 P_SpawnSkidDust(p, 48<<16) end
		else
			P_SpawnMobjFromMobj(s, 0,0,0, MT_EXPLODE)
		end
		P_TryMove(s, s.x+FixedMul(16*s.scale, cos(RAILANG)), s.y+FixedMul(16*s.scale, sin(RAILANG)), true)
		P_InstaThrust(s, RAILANG, 3*s.scale)			
		if convertmomentum=="emergency"
			s.momz = 0
		else
			local LASTZ = GS.LastGrindZ if LASTZ==nil LASTZ = s.z end
			local LMOMZ = s.z-GS.LastGrindZ
			if (sameXY=="jump" or sameXY=="sidehop")
				local SCALE = FixedDiv(FixedMul(p.jumpfactor or 1, s.scale), FRACUNIT)
				local SPEEDJUMP = min(28*SCALE, abs(GS.railspeed/3))-(36*SCALE)
	--			print("SPEEDJUMP:\x85 "+SPEEDJUMP/FRACUNIT)
				P_Thrust(s, RAILANG+ANGLE_180, SPEEDJUMP)			
				s.momz = (LMOMZ/3)-(5*s.scale*ZFLIP)
	--			print("MOMZ:\x81"+s.momz/FRACUNIT)
			else
				S_StartSound(s, sfx_s1c0)
				s.momz = (LMOMZ/4)+( (LOOP and LOOP.walljump) and -2*s.scale*ZFLIP or 3*s.scale*ZFLIP )
			end
		end
		GS.cannotgrind = max(GS.cannotgrind or 0, 5)
	end
	s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ|MFE_JUSTSTEPPEDDOWN)
	
	--Carry rail momentum back into gameplay?
	if convertmomentum
	and not (GS.leftrail and not (JUMPRAMP and (JUMPRAMP!=2)))
	and not (VERTRAIL)
		if rail and rail.valid
			local GRINDING = GS.grinding or 0
			if CONVEYOR
				if (sameXY!="jump")
					CONVEYOR = $/(((sameXY=="sidehop") and 4 or 2)*(GS.IgnoreConveyor and 4 or 1))
				end
			end
			--Teleport to the end of the rail or fling from where you are currently?
			if not sameXY and not (GS.FORCEKICK)
				local RX,RY = rail.x,rail.y
				if (((GS.railspeed or 0)+CONV) >= 0) --Forward flings are a bit more complex
					if rail.GSnextrail and rail.GSnextrail.valid
						RX,RY = rail.GSnextrail.x, rail.GSnextrail.y
					else
						RX = $-FixedMul(rail.GSrailXYlength, cos(RAILANG))
						RY = $-FixedMul(rail.GSrailXYlength, sin(RAILANG))
					end
				elseif (CONVEYOR) and p
					local DIST1,DIST2 = FixedHypot(FixedHypot(rail.x-s.x, rail.y-s.y), rail.z-s.z), nil
					if rail.GSnextrail and rail.GSnextrail.valid
						DIST2 = FixedHypot(FixedHypot(rail.GSnextrail.x-s.x, rail.GSnextrail.y-s.y), rail.GSnextrail.z-s.z)
						if (DIST2+(48<<16) < DIST1)
							RX,RY = rail.GSnextrail.x, rail.GSnextrail.y
							RAILANG = $+ANGLE_180 --Pretend it's backwards.
						end
					else
						RX,RY = s.x, s.y
					end
				end
				local RZ = RAIL.GetRailZ(rail, s, 0, nil, {X=RX, Y=RY})
				P_MoveOrigin(s, RX, RY, RZ) --Move to the appropriate end of the rail!
				GS.cannotgrind = max($,5)
				if CONVEYOR CONVEYOR = $*2/3 end
			end
			
			local ZFLING = 12
			local ALLSPEED = GS.railspeed+CONVEYOR --Calculations to appropriately scale speed from angled rails.
			local ADD = 0
			local MINSPEED = s.scale
			
			if GS.railspeed!=nil
				local ZDELTA = rail.GSrailZDelta
				if not (ZDELTA) and ((rail.GSrailXYlength or 0) < (1<<16))
					if (GS.railspeed < 0)
						if rail.GSnextrail and rail.GSnextrail.GSrailZDelta
							ZDELTA = rail.GSnextrail.GSrailZDelta
						end						
					elseif rail.GSprevrail and rail.GSprevrail.GSrailZDelta
						ZDELTA = rail.GSprevrail.GSrailZDelta
					end
				end
				if ZDELTA!=nil --Set speed reduction depending on the angle of rail stopping you.
				--	if ZDELTA and ((GS.railspeed or 0)<0) 
				--		ZDELTA = InvAngle($)
				--	end
					ADD = ZDELTA/4975000
					ZFLING = abs(ADD) --Hey, what works, works!
					if (ZFLING)	--Z Fling. Your Z speed affects your vertical speed.
						if (ZFLING < 40)
							ZFLING = (ZFLING < 20) and $*2/3 or $*3/4 --Less reduction on non-steep rails.
				--			RAIL.print("\x83 Reduced to" +ZFLING, 10)
						elseif (ZFLING > 110) --Extra reduction on super-steep rails.
							ZFLING = (ZFLING > 130) and $*3 or $*2
				--			RAIL.print("\x85 Increased to" +ZFLING, 10)
						end
						local LASTZ = GS.LastGrindZ2 if LASTZ==nil LASTZ = GS.LastGrindZ end
						if LASTZ!=nil
						and ( (s.eflags & MFE_VERTICALFLIP) and (GS.LastGrindZ < s.z)
						or not (s.eflags & MFE_VERTICALFLIP) and (GS.LastGrindZ > s.z) )
							if not p or not (abs(s.z-LASTZ) > ((p.cmd.forwardmove > 35) and 6 or 4)*s.scale)
								if sameXY=="jump"
									SLOPEFLING = -1
								end
								ZFLING = -max(-5, min(0, -$/(CONVEYOR and 24 or 16)))+8
							end
						end
					end
					if sameXY=="jump" --Jumps sacrifice horizontal speed for vertical height.
						if not (SLOPEFLING == -1)
							SLOPEFLING = ZFLING
							ADD = (abs(ALLSPEED) > 55<<16) and $*(s.scale/6) or ($*s.scale/4)
						else
							ADD = $*(s.scale/2)
						end
						if (GRINDING < 4)
							ZFLING = $+4-GRINDING
						end
					elseif sameXY=="sidehop" --Sidehops take advantage of slopes for thrust.
						ADD,CONVEYOR,ZFLING = $*(s.scale/24),0,6					
					else --Flings tend to go more horizontal.
						ADD = s.scale
						if (ZFLING >= 0) and (ZFLING < 12)
							ZFLING = 12
						end
						if (GRINDING < 10)
							ZFLING = $+(22-(GRINDING*2))
							if (abs(ALLSPEED) < 41*s.scale) and not (CONVEYOR)
								MINSPEED = abs(ALLSPEED)
							end
						end	
					end
				else --No slope AT ALL?
					if sameXY!="jump" and sameXY!="sidehop"
						ADD = s.scale
						if (ZFLING >= 0) and (ZFLING < 12)
							ZFLING = 12
						end	
						if (GRINDING < 10)
							ZFLING = $+(22-(GRINDING*2))
							if (abs(ALLSPEED) < 41*s.scale) and not (CONVEYOR)
								MINSPEED = abs(ALLSPEED)
							end
						end
					end
				end
				local THRUST = 0
			--	print("FLY:"+(ALLSPEED/s.scale)+" ZFLING:"+ZFLING+" ADD:"+ ADD+" GRINDING:\x81"+GRINDING)
			
				if (abs(ALLSPEED) < 50*s.scale)
					ZFLING = $/2
				elseif (abs(ALLSPEED) > 80*s.scale)
					ZFLING = (abs(ALLSPEED) > 105*s.scale) and $+2 or $+1
				end
				if ALLSPEED
					if JUMPRAMP==2 and (ZFLING > 0) --Frontiers rails will jump further!
						THRUST = FixedDiv(FixedMul(ALLSPEED or 1, 140), 140+ZFLING)
					else
						THRUST = FixedDiv(FixedMul(ALLSPEED or 1, 99), 99+ZFLING)
					end
				end
				if MINSPEED
					if (sameXY!="sidehop") and (abs(CONV) > abs(MINSPEED))
						if (CONV < 0)
							MINSPEED = min($, CONV/2)
						else
							MINSPEED = max($, CONV/2)
						end
					end
					if (THRUST < 0) 
						if (-abs(MINSPEED) < THRUST)
							THRUST = -abs(MINSPEED)
						end
					elseif (MINSPEED > THRUST)
						THRUST = MINSPEED
					end
				end		
				local FLINGANG
				if (GS.railspeed+CONVEYOR < 0)
					FLINGANG = RAILANG
				else
					FLINGANG = RAILANG+ANGLE_180
				end	
				P_InstaThrust(s, FLINGANG, abs(THRUST)+((THRUST>=0) and ADD or -ADD)) --How far do we fly?
			--	P_InstaThrust(s, RAILANG+ANGLE_180, THRUST+((THRUST>=0) and ADD or -ADD)) --How far do we fly?
				
				--Ground snapping would be really annoying when manually jumping off rails.
				if not sameXY
			--		print("sameXY:\x82"+sameXY+"\x80 s.z"+s.z>>16+" GS.LastGrindZ:"+(GS.LastGrindZ or 0)>>16+" GS.LastGrindZ2:"+(GS.LastGrindZ2 or 0)>>16)
					local hof = (rail.GShangrail) and 48*s.scale*ZFLIP or 0 --Snap to floors easier on hangrails.
					
					if ( 
					(s.z+s.momz-hof <= s.floorz+60*s.scale) and not (s.eflags & MFE_VERTICALFLIP) --Snap to the floor/ceiling?
					or (s.eflags & MFE_VERTICALFLIP) and (s.z+s.momz-hof >= s.ceilingz-90*s.scale) 
					) 
					and not (ZFLING > 25) and not (LOOP and LOOP.walljump) --Now if we're flinging skywards!
					and not (rail.GSVertRail)
						local OGZ = s.z
						if not (s.eflags & MFE_VERTICALFLIP)
							s.z = s.floorz+1
							s.momz = min($, -3*s.scale)
						else
							s.z = s.ceilingz-1
							s.momz = max($, 3*s.scale)
						end
						s.pmomz = 0
						s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ)
						GS.leftrail = 3
						LTAB.snappedtofloor = true
						if p
							LTAB.OGZ = OGZ
							p.powers[pw_justlaunched] = 2
						end
					elseif GS.LastGrindZ != nil --Now add slope momentum vertically 
						local TMOMZ = s.z-GS.LastGrindZ
						if GS.LastGrindZ2!=nil
							if TMOMZ == 0
								TMOMZ = s.z-GS.LastGrindZ2
							else
								local TMOMZ2 = s.z-GS.LastGrindZ2
								if (TMOMZ < 0) and (TMOMZ2 < TMOMZ)
								or (TMOMZ > 0) and (TMOMZ2 > TMOMZ)
									TMOMZ = ($+TMOMZ2)/2 --Take the average of the two.
								elseif (TMOMZ*ZFLIP < 0) and (TMOMZ2*ZFLIP > 0)
									TMOMZ = abs(GS.LastGrindZ-GS.LastGrindZ2) --Cheeky little shit, are we?
								end
							end
						end
						if TMOMZ --Maximum and minimum values for sanity. 
							local sped = max(0, 28-abs(ALLSPEED/(3*s.scale)) ) --Height reduction for going slow.
							if (JUMPRAMP!=2) and (GRINDING < 8)
								sped = $+(8-GRINDING)
							end
							local SPEED = abs(s.momx)+abs(s.momy)
							if (SPEED > s.scale)
								local DIV = min(60, max(-20, TMOMZ/s.scale))
								if (GRINDING < 14)
									if (DIV > 0) --If you haven't been grinding long, don't retain as much speed from upwards slopejumps.
										DIV = $+(28-(GRINDING*2))
									elseif (DIV < 0) --On the other hand, downward slopes can be beneficial in a way.
										DIV = $-(9+(GRINDING/2))
									end
								end
								if (DIV) --Horizontal slopejump momentum adjustments.
								and not ((DIV < 0) and (SPEED > (80-DIV)*s.scale))
									if (s.state==S_PLAY_RIDE) and sameXY!="sidehop" and not (GS.railswitching) 
										s.momx,s.momy = FixedDiv(FixedMul($, 70), 70+DIV), FixedDiv(FixedMul($, 70), 70+DIV)
									elseif (DIV > 0) 
									or (sameXY=="jump") and not (JUMPRAMP and (DIV > 0))
										s.momx,s.momy = FixedDiv(FixedMul($, 111), 111+DIV), FixedDiv(FixedMul($, 111), 111+DIV)
									else
										s.momx,s.momy = FixedDiv(FixedMul($, 111), 111+DIV), FixedDiv(FixedMul($, 122), 122+DIV)
									end
								end
							end
							if (TMOMZ > 10*s.scale) --Diminishing returns.
								if (TMOMZ > 30*s.scale)
									if (TMOMZ > 38*s.scale)
										TMOMZ = max(38*s.scale, $*7/8)
									else
										TMOMZ = max(30*s.scale, $*8/9)
									end
								elseif (TMOMZ > 20*s.scale)
									TMOMZ = max(20*s.scale, $*9/10)
								else
									TMOMZ = max(10*s.scale, $*12/13)						
								end
							elseif (TMOMZ < -6*s.scale)
								if (TMOMZ < -14*s.scale)
									TMOMZ = min(-14*s.scale, $*7/8)
								else
									TMOMZ = min(-6*s.scale, $*8/9)
								end
							end
							TMOMZ = min(48*s.scale, max($, -30*s.scale)) --Absolute limits.	
							
							if sped and not (JUMPRAMP and (JUMPRAMP!=2) and (sped > 0))
								TMOMZ = FixedDiv(FixedMul($, 75), 75+sped)
							end
							SLOPEFLING = (TMOMZ*3/4)*ZFLIP
							LTAB.slopefling = SLOPEFLING
							if sameXY=="jump"
								if ZFLIP==-1 and ((ZSPEED or 0) < 0)
									s.momz = $-SLOPEFLING
									LTAB.slopefling = -SLOPEFLING
								else
									s.momz = $+SLOPEFLING --And finally, our vertical slope momentum!
								end
								LTAB.nojumpbuffer = true
							else
								s.momz = $+(SLOPEFLING*5/6)
							end
							if p and (GRINDING < 5) and not (p.pflags & (PF_STARTJUMP|PF_JUMPED)) --We LITERALLY just started.
							and not (JUMPRAMP and (JUMPRAMP!=2))
								s.momz = (GRINDING==4) and $/2 or $/5
							end
						end
					end
				end
				if p
					if ((SLOPEFLING*ZFLIP) > 7*s.scale) --Move back to better follow the action.
						p.powers[pw_justlaunched] = 2
						if GS.railcam and GS.railcam.valid
							GS.railcam.noZ = 1 --halt cam Z movement for a tic.
						end
						P_TryMove(s, s.x-(s.momx*3), s.y-(s.momy*3), true)
						if s.skin --Stop some sounds
							S_StopSoundByID(s, skins[s.skin].soundsid[SKSJUMP])
							p.pflags = $|PF_JUMPDOWN
						end	
					end
					if SKIN and SKIN["PostFling"] and SKIN["PostFling"](p, s, GS, rail, sameXY, SLOPEFLING or 0, JUMPRAMP) end
				end
			--	print("RAILSPEED:\x88"+GS.railspeed/FRACUNIT+"\x80 SLOPEFLING:\x88"+SLOPEFLING/FRACUNIT+"\x80 s.momz:\x88"+s.momz/FRACUNIT) 
			--	print("LASTZ:\x89"+GS.LastGrindZ/FRACUNIT+"\x80 s.z:\x82"+s.z/FRACUNIT+"\x80 LASTZ2:\x86"+(GS.LastGrindZ2 or 0)/FRACUNIT+"\x80 ZFLING:\x8a"+ZFLING) 				
			end
		end
	--	if p and not sameXY and not p.powers[pw_justlaunched]==2
	--		RAIL.flingjump(s, GS) --Have a backup, maybe?
	--	end 
	end
	--Remove rail values and reset state where needed.	
	if RAIL.GrindState(s, "hang") 
		GS.cannotgrind = max($,11)	
		LTAB.timer = max($, 24)
	else
		GS.cannotgrind = max($, (SLOPEFLING<0) and 3 or 5)
	end	
	if not (sameXY) and rail and GS.grinding --Don't get stuck on the same rail due to lack of speed!
	and not (rail.GSVertRail)
		local FLINGANG = s.angle
		if (GS.railspeed+CONV < 0)
			FLINGANG = RAILANG
		else
			FLINGANG = RAILANG+ANGLE_180
		end	
		local DISTMOVE = (CONV) and 85<<16 or min(85<<16, max(0, (85<<16)-(abs(s.momx)+abs(s.momy))))
		if (DISTMOVE!=0)
			P_TryMove(s, s.x+FixedMul(DISTMOVE, cos(FLINGANG)), s.y+FixedMul(DISTMOVE, sin(FLINGANG)), true)
		--	print("DISTMOVE TP:\x81"+DISTMOVE/FRACUNIT)
		end
	end

	LTAB.MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
	local RAILNUM = nil if rail and type(rail.GSrailnum)=="number" RAILNUM = rail.GSrailnum end

	GS.leftrail = max(GS.leftrail or 0, 2) --For misc purposes, guiding behavior after leaving a rail.
	
	GS.myrail,GS.railprogress,GS.turntoia,GS.grinddirection,GS.LastGrindZ2,GS.exittic,GS.railsparks,GS.MyRailStats = nil --DELET
	GS.ropesnap,GS.FORCEKICK,GS.forcetrailcolor = nil
	GS.grinding,GS.railspeed,s.spritexoffset,s.spriteyoffset,s.rollangle = 0,0,0,0,0 --nein
	
	GS.justtouchedrailthing,GS.railfalltimer = max(GS.justtouchedrailthing or 0, 3), max(GS.railfalltimer or 0,3)
	GS.LTAB = LTAB --Keeps track of stats after we left the rail.
	
	--Player specific stuff.
	if p
		if s.GSarrow --Remove any sidehop arrows immediately.
			if s.GSarrow.valid 
				if s.GSarrow.arrow and s.GSarrow.arrow.valid P_RemoveMobj(s.GSarrow.arrow) end
				P_RemoveMobj(s.GSarrow)
			end
			s.GSarrow = nil
		end	
		GS.maxroll,GS.perfectcorner,GS.railtilt,GS.TWOD,GS.viewrollangle,GS.VRplus,GS.homingANG,GS.walljump = nil --DELET2
		GS.rollangle,p.viewrollangle = 0,0
		p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_SPINNING|PF_STARTDASH)
		
		if GS.crouchgrind
			s.spritexscale,s.spriteyscale = FRACUNIT,FRACUNIT
		end	
		if p.powers[pw_carry]==3888 --CR_GRINDRAIL
			p.powers[pw_carry] = 0
		end
		
		if sameXY=="sidehop"
			if not (RAIL.GrindState(s, "hang"))
				s.momz = $/2
			end
			if GS.LastRail and GS.LastRail.GSVertRail
				s.momx,s.momy = $/-8,$/-8
			end
		elseif sameXY=="jump"
			GS.LTAB.nojumpbuffer = true
			if LOOP and LOOP.walljump
				local MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
				if not (LOOPFLAGS & (262144|524288)) and not (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1))
					p.drawangle = MOMANG
					s.angle = MOMANG
				end
				SLOPEFLING = 0
				local SCALE = FixedDiv(FixedMul(s.scale, LOOP.walljump), max(p.jumpfactor, FRACUNIT)) 
				if (s.eflags & MFE_VERTICALFLIP)
					if (s.momz > -20*SCALE) s.momz = -20*SCALE end
				elseif (s.momz < 20*SCALE)
					s.momz = 20*SCALE
				end
				P_InstaThrust(s, MOMANG, max(p.speed, 43*SCALE))
				GS.walljump = {timer = 18, lastwall = RAILNUM, init = 0, loopcam = 0, jumpang = MOMANG}
				if (LOOPFLAGS & (262144|524288)) --Sidecam.
					GS.forcemove = {timer = 5, forward = 0}
					p.cmd.sidemove,p.cmd.forwardmove = 0,0
					GS.walljump.loopcam = (LOOPFLAGS & 262144) and 1 or -1
					if p.GSANflags==nil and not (p.pflags & PF_ANALOGMODE)
						p.GSANflags = (p.pflags & (PF_DIRECTIONCHAR|PF_ANALOGMODE))
						p.pflags = $|PF_DIRECTIONCHAR|PF_ANALOGMODE
					end
				end
				if (abs(s.momx)+abs(s.momy) > 33*s.scale)
					GS.cannotgrind = min($, 2)
				else
					GS.cannotgrind = max($, 6)
				end
				GS.docam = (GS.walljump.loopcam) and max(GS.docam or 0, 80) or 0
			elseif (LOOPFLAGS & (262144|524288)) and GS.railcam and GS.railcam.valid
				p.cmd.forwardmove = 0
				GS.docam = max(GS.docam or 0, (GRINDFLAGS & 524288) and 30 or 17)+4
			end
		else
			if RAIL.GrindState(s, "any") --Undo grinding state if we're in one.
			or ((s.sprite2 & ~SS2) == SPR2_GRND) --Using some custom state...?
			or (VERTRAIL)
				--Return player to their proper animation!
				if not (s.health) or p.playerstate==PST_DEAD
					GS.cannotgrind = max($, 99)
					if s.skin != "adventuresonic" --This guy ragdolls, but others won't.
						s.momx,s.momy = 0,0
						s.state = S_PLAY_DEAD
					end
				elseif P_PlayerInPain(p) --how did we get here
					s.state = (p.powers[pw_super]) and S_PLAY_STUN or S_PLAY_PAIN					
				elseif (LTAB.snappedtofloor or (GS.leftrail==3 or P_IsObjectOnGround(s)) )
				and not (VERTRAIL)
					if not P_IsObjectOnGround(s) --Need a little nudge?
						if ZFLIP==-1
							s.momz = max(4*s.scale, $+4*s.scale)
						else
							s.momz = min(-4*s.scale, $-4*s.scale)
						end
					elseif not (p.pflags & (PF_SPINNING|PF_JUMPED))
						s.eflags = $|MFE_JUSTHITFLOOR
					end
					GS.leftrail = 4
					s.state = (p.speed > 32*s.scale) and S_PLAY_RUN or p.speed and S_PLAY_WALK or S_PLAY_FALL
					if not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_FULLSTASIS))
						if (p.cmd.buttons & BT_JUMP) and (GS.backupjump < 22)
							P_DoJump(p, true)
							GS.LTAB.OGZ, GS.LTAB.snappedtofloor = nil
						elseif (p.cmd.buttons & BT_SPIN) and (GS.backupspin < 22)
							p.pflags = $ & ~PF_USEDOWN
						end
					end
					if (LTAB.OGZ!=nil)
						local DIST, ghs = abs(s.z-LTAB.OGZ)
						local STARTNUM = (DIST > 50*s.scale) and 1 or (DIST > 36*s.scale) and 2 or 3						
						for i = STARTNUM,3
							ghs = P_SpawnGhostMobj(s)
							if ghs and ghs.valid
								ghs.scale = s.scale
								ghs.blendmode = AST_ADD
								ghs.spritexscale,ghs.spriteyscale = s.spritexscale,s.spriteyscale
								ghs.colorized = s.colorized
								ghs.fuse,ghs.tics = 5+i,6+i
								ghs.scalespeed = s.scale/ghs.tics
								ghs.destscale = 1
								P_Thrust(ghs, p.drawangle, -6*i*s.scale)
								if i==1
									ghs.z = LTAB.OGZ
									ghs.scale = $/2
									ghs.flags2 = $|MF2_SHADOW
								elseif i==2
									ghs.z = s.z+((DIST/2)*ZFLIP)
									ghs.scale = $*2/3
								end
							end
							P_SpawnSkidDust(p, (55-(i*5))<<16)
						end
						S_StartSoundAtVolume(s, sfx_cdfm17, 225-(STARTNUM*25))
					end
				else
					if not P_IsObjectOnGround(s)
						if ( ((s.sprite2 & ~SS2)==SPR2_ROLL) or (LOOP and (LOOPFLAGS & 131072)))
						and not (GS.LastRail and GS.LastRail.GSnorolling)
							s.state =  S_PLAY_ROLL
							if p.charability2==CA2_SPINDASH
								p.pflags = $|PF_SPINNING
							end	
						else
							s.state =  (s.momz*ZFLIP > s.scale*9) and S_PLAY_SPRING or S_PLAY_FALL
						end
					else
						s.state =  (s.momz*ZFLIP > s.scale*9) and S_PLAY_SPRING or S_PLAY_FALL
						if not (p.pflags & (PF_SPINNING|PF_JUMPED))
							s.eflags = $|MFE_JUSTHITFLOOR
						end
					end
					if LOOP and LOOP.walljump
						if GS.LastRail and GS.LastRail.GSVertRailTopZ!=nil and (abs(s.z-GS.LastRail.GSVertRailTopZ) < 238<<16)
							if (s.momz*ZFLIP < 10*s.scale)
								s.momz = 10*s.scale*ZFLIP
							end
							for i = 1,3 P_PlayerZMovement(s) end
							if GS.LastRail.GSVertRailDOWNANG!=nil --Do a getup anim.
								local CEILINGZ = (s.eflags & MFE_VERTICALFLIP) and s.floorz or s.ceilingz
								if (abs((s.z+s.momz)-CEILINGZ) > 50*s.scale)
									local ANG = GS.LastRail.GSVertRailDOWNANG+ANGLE_180
									s.angle,p.drawangle = ANG,ANG
									P_InstaThrust(s, ANG, 6*s.scale)
									GS.LTAB.GetUpAnimANG = ANG
									if not p.powers[pw_nocontrol] p.powers[pw_nocontrol] = 3 end		
								end
							end
							GS.cannotgrind = max($, 14)	
							p.pflags = $|PF_JUMPED|PF_JUMPDOWN|PF_USEDOWN & ~(PF_THOKKED|PF_SHIELDABILITY|PF_SPINNING)
							s.state =  S_PLAY_JUMP
							s.eflags = $ & ~MFE_JUSTHITFLOOR
							p.cmd.forwardmove,p.cmd.sidemove = 0,0
							if GS.forcemove==nil GS.forcemove = {timer = 3, forward = 0, sidemove = 0} end
						end
						GS.cannotgrind = max(GS.cannotgrind or 0, 11)
						GS.LTAB.nojumpbuffer = true
						GS.LTAB.snappedtofloor = nil
						p.pflags = $|PF_JUMPDOWN
						
					elseif GS.LastRail and GS.LastRail.GSVertRail --and GS.LastRail.GSforceUDMFang and 
						if (s.momz*ZFLIP > 0)
							if GS.TWOD==true
								s.momx,s.momy = 0,0
							elseif (s.momx or s.momy)
								local ANG = p.drawangle
								if (abs(p.cmd.sidemove)+abs(p.cmd.forwardmove) > 38)
									ANG = (p.cmd.angleturn<<16)+(R_PointToAngle2(0, 0, p.cmd.forwardmove<<16, -(p.cmd.sidemove<<16)))
								end
								P_InstaThrust(s, ANG, max(4*s.scale, ((abs(s.momx)+abs(s.momy))/3)))
								GS.LTAB.MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
							end
						end
						GS.cannotgrind = max(GS.cannotgrind or 0, 9)
						GS.LTAB.snappedtofloor = nil
						if (p.pflags & PF_SPINNING)
							if not (p.pflags & (PF_JUMPED|PF_STARTJUMP|PF_NOJUMPDAMAGE|PF_STARTDASH)) and not P_IsObjectOnGround(s)
								p.pflags = $|PF_THOKKED
							end
						else
							GS.LTAB.nojumpbuffer = true
							p.pflags = $|PF_JUMPDOWN
						end
					end
				end
			end
			if (p.cmd.forwardmove > 28) and not (GS.LastRail and GS.LastRail.VertRail)
			and not (LOOP and LOOP.walljump) and not ((GS.docam or 0)>5) and (GS.TWOD!=true)
				if GS.holdF or (LOOPFLAGS & (524288|262144)) and LOOP
					local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceiling or s.floorz
					if ((abs(s.z-FLOORZ) < 99*s.scale) or LTAB.snappedtofloor)
					and not (p.pflags & (PF_JUMPED|PF_STARTJUMP)) and not GS.walljump
						GS.forcemove = {timer = 6, forward = 50, sidemove = 0} --Why are you holding forward? Whatever, force it
						s.angle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
					end
				end
			end
		end
		if not (JUMPRAMP)
			local AIRDRAG = 44 if STATS and (STATS.AIRDRAG!=nil) AIRDRAG = STATS.AIRDRAG end
			if (AIRDRAG!=0) GS.airdrag = max(GS.airdrag or 0, AIRDRAG) end
		end
		if GS.LastRail and ((GS.LastRail.GSloopflags or 0) & 8388608)
			if GS.railcam and GS.railcam.valid if p.awayviewmobj==GS.railcam p.awayviewmobj = nil end P_RemoveMobj(GS.railcam) end
			if GS.docam GS.docam = 0 end
		end
		GS.LTAB.laststate = s.state
		GS.LTAB.thrust = abs(s.momx)+abs(s.momy)
		p.speed = GS.LTAB.thrust
		
		GS.holdF = nil
		GS.railjump,GS.railspin,GS.railc3 = ((GS.walljump and GS.walljump.timer) and 77<<16 or 99<<16),99<<16,99<<16
		
		if SKIN and SKIN["PostExit"] and SKIN["PostExit"](p, s, GS, GS.LastRail or rail, sameXY, SLOPEFLING, JUMPRAMP) end
	end
	return SLOPEFLING --
end

--Are you on a hangrail? Make sure s and rail are valid!
RAIL.HangRail = function(s, rail)
	if rail.GSmainrail rail = rail.GSmainrail end
	if rail.GShangrail
		if not (s.eflags & MFE_VERTICALFLIP)
			return true --
		end
	elseif (s.eflags & MFE_VERTICALFLIP)
		return true --
	end return false --
end

--Returns the current curve angle on a line.
/*RAIL.XYCurve = function(ANGLE, DISTANCE, TOTALDISTANCE)
    local MIDPOINT = TOTALDISTANCE / 2
    local MULTIPLIER = 0

    -- Determine the multiplier based on the distance from the midpoint
    if DISTANCE > MIDPOINT then
        MULTIPLIER = DISTANCE - MIDPOINT
    elseif DISTANCE < MIDPOINT then
        MULTIPLIER = MIDPOINT - DISTANCE
    end
    -- Calculate the curve angle
    local curveAngle = 0
    if MULTIPLIER ~= 0 then
        curveAngle = (ANGLE / TOTALDISTANCE) * MULTIPLIER
    end
    return curveAngle
end */

--Returns the current curve angle on a line.
RAIL.XYCurveAngle = function(ANGLE, DISTANCE, TOTALDISTANCE)
    local MIDPOINT = TOTALDISTANCE / 2
    local MULTIPLIER = abs(DISTANCE - MIDPOINT)

    -- Calculate the curve angle based on the distance from the midpoint
    local curveAngle = (ANGLE / MIDPOINT) * MULTIPLIER

    -- Adjust the curve angle to ensure a smooth transition at the midpoint
    if DISTANCE > MIDPOINT then
        curveAngle = ANGLE - curveAngle
    end
    return curveAngle
end

--What's our XY distance from the rail's origin? Used for snapping to rails.
RAIL.GetRailXYDistFromOrigin = function(s, rail)
	if rail==nil return s.x,s.y,1 end --
	local RAILANG = RAIL.ANG(rail)	
	local LENGTH = rail.GSrailXYlength or 1
	local dist = min(FixedHypot(s.x-rail.x, s.y-rail.y), LENGTH)
	local snapX = rail.x+FixedMul(min(LENGTH-1, dist), cos(RAILANG+ANGLE_180) )
	local snapY = rail.y+FixedMul(min(LENGTH-1, dist), sin(RAILANG+ANGLE_180) )
	return snapX, snapY, dist --dist is the XY distance together.
end


--Gradually turn towards destang. Return the amount we should turn. destANG is mandatory.
RAIL.GradualTurn = function(s, DESTANG, STARTANG, MAXTURN)
	MAXTURN = (MAXTURN) or ANG2*2
	STARTANG = (STARTANG) or s.angle
	local ANGLE,PLUS = RAIL.AngleDifference(DESTANG, STARTANG, 2, MAXTURN)
	return ANGLE,PLUS
end

--Retrieve the Rail Slope Z point. SlopeZ. washanging and speed fields are optional.
RAIL.GetRailZ = function(rail, s, speed, washanging, COORDS, SPECIAL)
	local GS	
	if (s!=rail) and (s.type!=MT_GSRAIL) and (s.type!=MT_GSRAILSEG)
		GS = s.GSgrind 
		if GS==nil
			RAIL.SetupMobjRailVars(s) GS = s.GSgrind
		end if GS==nil GS = {} end
	end
	if rail and rail.GSmainrail
		rail = rail.GSmainrail
	elseif (GS) and GS.myrail
		rail = GS.myrail
	end
	
--	local RAILANG = RAIL.ANG(rail)
	local RailZ = 0
	if (GS) 
		if GS.grinding or SPECIAL=="ATTACH"
			if (s.eflags & MFE_VERTICALFLIP)  --Vertical offsets depending on the situation.
				if (washanging or rail.GShangrail or rail.GSupsidedown) and SPECIAL!="ATTACH" --Grinding, but upside down!
					local ADD = 80*s.scale
					if rail.GSinfo and rail.GSinfo.hangZoffset
						ADD = $-(rail.GSinfo.hangZoffset*s.scale) 
					end
					RailZ = $-ADD
				else --Hanging while upside down on normal rails.
					RailZ = $+3*s.scale
				end
			elseif (washanging or rail.GShangrail or rail.GSupsidedown) --hang-sliding objects should hang onto the bottom of the rail, obviously.
				local ADD = 86*s.scale
				if rail.GSinfo and rail.GSinfo.hangZoffset
					ADD = $-(rail.GSinfo.hangZoffset*s.scale) 
				end
				RailZ = $-ADD
			end
		end
	end
	if rail.floorspriteslope and rail.floorspriteslope.zdelta --Could use rail.GSrailZDelta...?
		local slope,X,Y = rail.floorspriteslope
		if type(COORDS)=="table"
			X = COORDS.X Y = COORDS.Y
		else
			X,Y = s.x,s.y
		end
		local dist = FixedMul(X-slope.o.x, slope.d.x) + FixedMul(Y-slope.o.y, slope.d.y)
		if (abs(rail.floorspriteslope.zdelta) > 56*1193)
			RailZ = $+rail.z+((rail.floorspriteslope.zdelta < 0) and -dist or dist)			
		else
			RailZ = $+(slope.o.z+FixedMul(dist, slope.zdelta))	
		end
	else
		RailZ = $+rail.z
	end
	return RailZ --This is your FUTURE!
end

--Returns 1 if grinding forwards, or -1 if grinding backwards, similar to P_MobjFlip.
--If FACEANGLE is true, it'll use the angle the player faces instead than their momentum!
RAIL.GrindFlip = function(s, rail, FACEANGLE)
	if s and rail and s.GSgrind and (s.GSgrind.raildirection!=nil)
		local GS = s.GSgrind
		local ANG = RAIL.ANG(rail)
	--	if rail.GSRailCurve and (GS.railprogress!=nil)
	--		ANG = $+ (RAIL.RailXYCurveAngle(rail, (GS.railprogress) or 1, rail.GSrailXYlength))
	--	end
		local ANGDIFF = (ANG+ANGLE_180)-(ANG+GS.raildirection)
		local CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
		ANGDIFF = min(360-FixedInt(AngleFixed($)), FixedInt(AngleFixed($)) )
		
		if (FACEANGLE)
			return (ANGDIFF > 90) and -1 or 1 --Use it with your angle in mind instead.
		end
		if (ANGDIFF > 90) --Reverse rail momentum!
			return (GS.railspeed+CONVEYOR > 0) and 1 or -1
		else
			return (GS.railspeed+CONVEYOR < 0) and -1 or 1
		end
	end return 1 --
end

--Set mobj's angle to the rail's angle and change momentum accordingly.
RAIL.MobjToRailAngle = function(s, rail, angle, MOMENTUMSHIFT, SPRING)
	--Difference between given angle and rail angle in normal-ass humanly readable numbers.
	local GS = s.GSgrind if GS==nil RAIL.SetupMobjRailVars(s) GS=s.GSgrind end
	local CURVEANG = RAIL.ANG(rail)
--	if rail.GSRailCurve and (GS.railprogress!=nil)
--		CURVEANG = $+ (RAIL.RailXYCurveAngle(rail, (GS.railprogress) or 1, rail.GSrailXYlength))
--	end

	local ANGDIFF = (CURVEANG+ANGLE_180)-angle 
	local CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
	ANGDIFF = min(360-FixedInt(AngleFixed($)), FixedInt(AngleFixed($)) ) --Range is 0 to 180. Anything past 90 is reverse.
	
	if (ANGDIFF > 90) --Reverse rail momentum!
		if (GS.railspeed+CONVEYOR > 0)
			GS.railspeed = -$
		end	
		ANGDIFF = abs($-180)
		GS.raildirection = 0
	else
		if (GS.railspeed+CONVEYOR < 0)
			GS.railspeed = -$ 
		end		
		GS.raildirection = ANGLE_180
	end
	if MOMENTUMSHIFT and (ANGDIFF > 5)
		if (ANGDIFF > 50) --Slow down ALOT
			GS.railspeed = FixedDiv(FixedMul($+CONVEYOR, 25), 25+ANGDIFF)
		else --Minor slowdown.
			GS.railspeed = FixedDiv(FixedMul($+CONVEYOR, 50), 50+ANGDIFF)
		end
	end
--	RAIL.print("ANGDIFF: "+ANGDIFF+" New speed = "+FixedInt(GS.railspeed))
end

if RAIL.Bstates==nil RAIL.Bstates = {} end local BS = RAIL.Bstates --States that are considered "busy"
BS[S_PLAY_GASP] =			true
BS[S_PLAY_BOUNCE_LANDING] =	true
BS[S_PLAY_CLIMB] =			true
BS[S_PLAY_DRWN] =			true
BS[S_PLAY_DEAD] =			true
BS[S_PLAY_PAIN] =			"pain"
BS[S_PLAY_STUN] =			"pain"
for i = S_PLAY_SUPER_TRANS1,S_PLAY_SUPER_TRANS6
	if type(i)=="number" and (i) BS[i] = true end
end
for i = S_PLAY_NIGHTS_TRANS1,S_PLAY_NIGHTS_ATTACK
	if type(i)=="number" and (i) BS[i] = true end
end

--Probably can't interact with rails like this.
RAIL.busystates = function(s)
	if BS[s.state]==true
		return true --
	elseif BS[s.state]=="pain" and (s.momz*P_MobjFlip(s) >= -3*s.scale) and not (s.player and (s.player.pflags & PF_SLIDING))
		return "pain"
	elseif s.player
		if s.player.powers[pw_super]==1
		or (s.eflags&MFE_SPRUNG) and s.GSgrind and s.GSgrind.leftrail
		or s.player.powers[pw_flashing]==105
		//(s.state == S_PLAY_SPRING or s.state==S_PLAY_FALL and (s.momz*P_MobjFlip(s) > 1)) 
			return 1 --
		end
	end
end

--Global blockmap list. Can be extended by other mods.
if RAIL.AttachList==nil RAIL.AttachList = {} end local BM = RAIL.AttachList
for i = MT_RING,MT_BIGMINE
	if type(i)=="number" and (i) BM[i] = true end
end
for i = MT_EMERALD1,MT_EMERALD7
	if type(i)=="number" and (i) BM[i] = "float" end
end
BM[MT_POPUPTURRET]	= true
BM[MT_TURRET]		= true
BM[MT_STARPOST]		= true

BM[MT_TOKEN]="float" --Floating types float slightly over the rail.
BM[MT_EMBLEM]="float"
BM[MT_BLUETEAMRING]="float"
BM[MT_REDTEAMRING]="float"
BM[MT_RING]="float"
BM[MT_COIN]="float"
BM[MT_BLUESPHERE]="float"
BM[MT_BOMBSPHERE]="float"

BM[MT_REDBOOSTER]="booster" --Booster types act like...well, boosters.
BM[MT_YELLOWBOOSTER]="booster"

BM[MT_YELLOWSPRINGBALL] = "dislodge" --Dislodge types dislodge the player from the rail on touch
BM[MT_REDSPRINGBALL] = "dislodge"
BM[MT_BUMPER] = "dislodge"
BM[MT_NIGHTSBUMPER] = "dislodge"
BM[MT_NIGHTSDRONE] = "dislodge"
BM[MT_FAN] = "dislodge"
BM[MT_DUSTDEVIL] = "dislodge"

BM[MT_BIGMINE] = "damage" --Damage types are destroyed on touch, sometimes backfiring on players in the process.
BM[MT_TNTBARREL] = "damage"
BM[MT_PROXIMITYTNT] = "damage" 
BM[MT_SPIKE] = "spike" 
BM[MT_WALLSPIKE] = "spike"


--Alligns all items above or below the rail. Rails and segments do this when given the ambush flag.
RAIL.AllignItemsWithRail = function(m)
	if (gamestate!=GS_LEVEL)
	or not (m and type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid) 
		return --
	end
	if not (m.GSnextrail and m.GSnextrail.valid)
	or not m.GSrailXYlength and m.GSforceUDMFang==nil
		return --
	end
	local rail = m
	local XYDIST = max(1<<16, FixedHypot(rail.x-rail.GSnextrail.x, rail.y-rail.GSnextrail.y)) --How far to look.
	local ANG = RAIL.ANG(rail)
	local RETURN,BMTYPE = nil
   	local allignboosters,doallign = {}, 0 --Necessary because of stupid boosters.
	local RAILANG = ANG if RAILANG==0 RAILANG = 1 end
	local VERTRAIL = false --Becomes an angle if not false.
	if rail.GSVertRail
		XYDIST = 35<<16
		if rail.GSVertRailDOWNANG!=nil
			VERTRAIL = rail.GSVertRailDOWNANG
		elseif (rail.GSnextrail.z < rail.z)
			VERTRAIL = RAILANG //R_PointToAngle2(rail.x, rail.y, rail.GSnextrail.x, rail.GSnextrail.y)
		else
			VERTRAIL = RAILANG+ANGLE_180 //R_PointToAngle2(rail.x, rail.y, rail.GSnextrail.x, rail.GSnextrail.y)+ANGLE_180
		end
	end
   	searchBlockmap("objects", function(ref, t)
		if not (t and type(t)=="userdata" and userdataType(t)=="mobj_t" and t.valid) --Invalid.
		or t.GSgrind and t.GSgrind.railobject --Already attached.
			return nil  --Invalid!
		end
		local spn = t.spawnpoint
		if not ((spn) and type(spn)=="userdata" and userdataType(spn)=="mapthing_t" and spn.valid)
			return nil --Object must be placed by a mapper.
		end
		
		if (udmf)!=true
			if (spn.options & MTF_EXTRA) return nil end --Binary mapper doesn't want this attached!
		else
			if spn.pitch==7 or spn.roll==7 return nil end --UDMF Mapper doesn't want this attached!
			
			if spn.args and userdataType(spn.args)=="mapthing_t.args"
				if spn.args[9] and (spn.args[9]!=m.GSrailnum)
			--	type(spn.args)=="userdata" and userdataType(spn.args)=="mapthing.args"
			--	and spn.args[10] and type(spn.args[10])=="number" and (spn.args[10]!=m.GSrailnum)
					return nil --Mapper wants it to attach only to THIS railnumber!
				end
			end
		end
		BMTYPE = BM[t.type]
		if not (BMTYPE) --Special types that always work.
			if not (t.flags & (MF_PAIN|MF_SPRING|MF_FIRE|MF_MONITOR))
				return nil --Must have these flags.
			elseif (t.flags & (MF_SCENERY|MF_NOSECTOR|MF_NOCLIPTHING|MF_AMBIENT|MF_BOXICON|MF_ENEMY|MF_BOSS))
			or RAIL.NeverAttachTypes[t.type]==true --Unless given an exception...then, never!
			or t.player --Duh.
				return nil --Cannot have these properties.
			end
		end	
		if VERTRAIL==false --Use these coordinates to make angle detection more accurate.
			local BACKX,BACKY = rail.x+FixedMul(128<<16, cos(RAILANG)), rail.y+FixedMul(128<<16, sin(RAILANG))	
			local ANGDIFF = RAIL.AngleDifference(R_PointToAngle2(BACKX, BACKY, t.x, t.y), RAILANG+ANGLE_180, 2, ANG20*4)
			if (ANGDIFF > ANG1*7) and (m.GSrailallignobjects!=2)
				return nil --Doesn't line up with our rail's angle.
			else
				local DIST2 = FixedHypot(rail.x-t.x, rail.y-t.y)
				if (DIST2 > XYDIST)			
					return nil --Angle-adjusted distance exceeds rail length
				end
			end
		end		
		local GS = t.GSgrind 
		
		if GS==nil RAIL.SetupMobjRailVars(t) GS = t.GSgrind end
		local ogx,ogy,ogz,ogyoffset = t.x, t.y, t.z, t.spriteyoffset
		local NCHFLAG = (t.flags&MF_NOCLIPHEIGHT) and MF_NOCLIPHEIGHT or 0 
		local HANGRAIL = (m.GShangrail) and true or false
		
		t.flags = $|MF_NOGRAVITY|MF_NOCLIPHEIGHT & ~MF_FLOAT --Fly, my pretty...!
		if VERTRAIL==false and (BMTYPE!="spike")
			local ATTACHP = "ATTACH"
			if BMTYPE=="dislodge" or (t.flags & MF_SPRING) or BMTYPE=="damage" or BMTYPE=="booster"
				ATTACHP = nil --Change Attach Flag?
			end
			t.z = RAIL.GetRailZ(rail, t, 0, nil, nil, ATTACHP) --Vertical alignment.
			if BMTYPE=="float" --Pull ring objects and such EXACTLY onto the rail.
				local xyDIST = FixedHypot(m.x-t.x, m.y-t.y)
				P_SetOrigin(t, 
				rail.x-FixedMul(xyDIST, cos(ANG)),
				rail.y-FixedMul(xyDIST, sin(ANG)), t.z)
			end
		end
		GS.justtouchedrailthing = 9 --To ignore colissions.
		GS.myrail,t.GSmyrail = rail,rail --This'll be your reference rail!

		t.eflags = $ & ~(MFE_APPLYPMOMZ|MFE_JUSTHITFLOOR|MFE_PUSHED)
		t.shadowscale = 0 --The rail realistically would obscure this, so remove any object shadows.
		t.momx,t.momy,t.momz = 0,0,0 --Reset all potential momentum
		
		local OGz = t.z 
		if HANGRAIL --Invert depending on our needs.
			if VERTRAIL==false
				t.z = $-(abs((t.height/2))+(60<<16))
				if (t.flags & MF_SPRING) and not (t.info and t.info.mass) or BMTYPE=="float" or BMTYPE=="booster"
					if not (t.eflags & MFE_VERTICALFLIP)
						t.eflags = $|MFE_VERTICALFLIP
						if not t.GSgotflipped
							t.height = $*2 --Accomodate vertical hitbox for the player's hanging offset.
							t.GSgotflipped = true
						end
					elseif BMTYPE=="float" and not t.GSgotflipped
						t.z = $-abs(t.height) 
					end
				end
			end
		elseif BMTYPE=="float"
			if VERTRAIL==false
				local SCALE = max(t.scale, FRACUNIT) 
				if SCALE!=FRACUNIT 
					t.spriteyoffset = $+FixedDiv(FixedMul(11<<16, FRACUNIT), SCALE)
				else
					t.spriteyoffset = $+(11<<16)
				end 
				t.z = $-(((HANGRAIL) and 30 or 6)<<16)*P_MobjFlip(t)
			end
			t.flags = $|MF_NOCLIPHEIGHT|MF_NOGRAVITY
			NCHFLAG = MF_NOCLIPHEIGHT
		end
		
		t.flags = ($ & ~MF_NOCLIPHEIGHT)|NCHFLAG --Bring MF_NOCLIPHEIGHT to it's original setting.
		
		if not (t.flags & (MF_ENEMY|MF_BOSS)) or BMTYPE=="damage"

			if BMTYPE=="float" or (t.flags & (MF_PAIN|MF_FIRE)) and (BMTYPE!="spike") --These can be the usual.
				if BMTYPE=="float" and VERTRAIL==false
					t.spriteyoffset = $+( ((HANGRAIL) and -20 or 13)<<16 )
				end
				if not (t.GSrailobject)	--Don't do it twice, ofc.
					if t.radius
						t.radius = ($*3/2)+6<<16 --Enlarge radius a bit to make lining stuff up easier on mappers.
					end
					if t.height
						t.height = ($*3/2)+6<<16
					end
				end
			else --Make a special object that does the collision and Lua for us, overriding the original behaviour.
			
				if t.GSMyRailTracker
					ogz = t.z
					if t.GSMyRailTracker.valid P_RemoveMobj(t.GSMyRailTracker) end --Begone!
					t.GSMyRailTracker = nil
				end
				local ghs = P_SpawnMobjFromMobj(t, 0,0,0, MT_GSRAILATTACHEDTHING)
				RAIL.SetupMobjRailVars(ghs)
				ghs.GSrailobject = ghs
				ghs.tracer = t
				ghs.GSgrind.railobject = ghs
				ghs.GSgrind.justtouchedrailthing = 9
				ghs.angle = t.angle
				ghs.radius = (t.radius*3/2) + 16<<16
				ghs.height = (t.height*2) + ( ((t.height < 0) and -32 or 32)<<16 )
				ghs.ogflags = t.flags --Original flags.
				ghs.XS_DontSnapToMe = true
				t.XS_DontSnapToMe = true
				t.MyRailTracker = ghs
				if BMTYPE=="booster"  --Agh, REALLY, game?
					t.originalZ = ogz
					for i = max(1, doallign),(max(1, doallign)+8) --Add this to an array to allign the stupid booster segments.
						doallign = $+1
						allignboosters[i] = t
					end
				end
				if (t.flags & (MF_SPRING|MF_MONITOR)) --Allow homing if it's a spring.
					ghs.flags = $|MF_SPRING & ~MF_SOLID --Can't have solid for some reason.
				end
				t.flags = $|MF_NOCLIPTHING --Take over collission!
				t.GSMyRailTracker = ghs --Connect to tracker.		
			end
		end
		if (VERTRAIL!=false) --On vertical rails, move attached objects back a tiny bit depending on rail angle!
			if not (rail.GSinvisiblerail) and not (rail.flags2 & MF2_DONTDRAW) and (rail.sprite)
				local MR,ogx,ogy = t,t.x,t.y
				for MOVER = 1,3
					if MOVER==2 
						MR = t.tracer
					elseif MOVER==3 
						MR = t.target
					end
					if MR and MR.valid and not MR.player
						P_TryMove(MR, MR.x+FixedMul(23<<16, cos(VERTRAIL)), MR.y+FixedMul(23<<16, sin(VERTRAIL)), true)
					end
				end
			end
		else
			local MR = t.target
			for MOVER = 1,2
				if MR and MR.valid and not MR.player --Move our target and tracer with us, if it's not a player.
					MR.z = t.z
				end MR = t.tracer
			end
		end
		if not (t.flags2 & MF2_DONTRESPAWN) and (t.flags & (MF_SPECIAL|MF_MONITOR)) and spn --Adjust spawnpoint info?
			local MOVEDX,MOVEDY,MOVEDZ,MOVEDYOFF = nil --Move the spawnpoint coordinates too?		
			if t.x!=ogx MOVEDX = (t.x-ogx)/FRACUNIT end
			if t.y!=ogy MOVEDY = (t.y-ogy)/FRACUNIT end	
			if ogz!=t.z MOVEDZ = (t.z-ogz)/FRACUNIT end		
			if (t.spriteyoffset!=ogyoffset) MOVEDYOFF = (t.spriteyoffset-ogyoffset)/FRACUNIT end
			if MOVEDX
				spn.x = $+MOVEDX
			end
			if MOVEDY
				spn.y = $+MOVEDY
			end
			if MOVEDYOFF
				spn.z = $+MOVEDYOFF
			end
			if MOVEDZ
				spn.z = $+MOVEDZ
				if BMTYPE=="float" --Adjust for the "float" autoadjust flag...
					if udmf==true and spn.args and spn.args[0]==0
					or (spn.options & MTF_EXTRA) and (udmf!=true)
						spn.z = $+(24*((spn.options & MTF_OBJECTFLIP) and -1 or 1))
					end
				end
			end
		end
		GS.railobject,t.GSrailobject = rail,rail --alt identifier for Lua purposes.
		RETURN = true --We attached something!
  	end, m, m.x-XYDIST, m.x+XYDIST, m.y-XYDIST, m.y+XYDIST)
	
	--sigh, speedbooster objects aren't assigned tracers OR targets. FML
	if (doallign) and allignboosters
	
		local BSTYPE = {} 
		BSTYPE[MT_BOOSTERSEG] = true
		BSTYPE[MT_BOOSTERROLLER] = true 
		
		for m in mobjs.iterate(mobjs) --We don't need the original m anymore anyway.
			if not (type(m)=="userdata" and userdataType(m)=="mobj_t" and m.valid and BSTYPE[m.type]) continue end	
			
			for i = 1,max(1, doallign)
				local t = allignboosters[i] 
				if not (t and type(t)=="userdata" and userdataType(t)=="mobj_t" and t.valid and t.originalZ!=nil)
					continue --It...it doesn't exist?
				end
				if (abs(m.z-t.originalZ) < max(t.scale, 1<<16)) 
				and (FixedHypot(t.x-m.x, t.y-m.y) <= (28*max(1<<16, t.scale)) ) 
					if not m.tracer m.tracer = t end
					m.GStracer = t --Fine. I'll refer to you myself. 
					m.eflags,m.flags2 = t.eflags,t.flags2
					m.spriteyoffset = t.spriteyoffset
					m.z = t.z
					if (t.eflags & MFE_VERTICALFLIP) and (m.type==MT_BOOSTERSEG)
						m.spriteyoffset = $-16<<16 --Since we're using Z position for checks, better to use this.
					end
					doallign = max($-1,0) break --Success! No need to continue the pairs table scroll.
				end
			end
			if not (doallign) break end --Stop when we're done to save performance.
		end
	end
	return RETURN --
end

--Returns true if you're in a state that can be used for grinding. 
--"Hang" specifies if we're checking for hangrails instead of regular rails.
RAIL.GrindState = function(s, HANG)
	if HANG=="any"
		if (s.state==S_PLAY_RIDE) or (s.state==S_PLAY_GSGRINDING)
			return true --	
		end
	elseif (HANG)
		if s.state==S_PLAY_RIDE
			return true --
		end
	elseif (s.state==S_PLAY_GSGRINDING)
		return true --
	end
end

--Don't/do take damage from...?
RAIL.PlayerShouldDamage = function(s, i, so, DMG, DMGTYPE) if not (s and s.valid) return end --
	if DMGTYPE==DMG_CRUSHED
		local GS = s.GSgrind
		if GS
			if (GS.grinding)
			or ((GS.leftrail or 0)>1) and (GS.leftrail < 5)
				return false --
			end
		end
	end 
	return nil --
end

--When a player takes damage, then...?
RAIL.PlayerDamage = function(s, i, so, DMG, DMGTYPE) if not (s and s.valid and s.health) return nil end --
	local GS = s.GSgrind
	if GS
		local p = s.player
		GS.cannotgrind = max(GS.cannotgrind or 0, 12)
		if not ((GS.cannotgrind or 0)>14) and not (GS.leftrail) and not (GS.failroll) and p
			local PANG, ANG = p.drawangle, p.drawangle
			if GS.grinding
				if ((GS.grinding < 3) or (GS.myrail and GS.myrail.GSVertRail))
					local ANG = PANG
					if GS.myrail and GS.myrail.GSVertRailDOWNANG!=nil
						ANG = GS.myrail.GSVertRailDOWNANG
					end
					if not GS.leftrail
						if p.powers[pw_flashing]==0 p.powers[pw_flashing] = 2 end
						RAIL.ExitRail(s)
						if not GS.leftrail GS.leftrail = 4 end
						if p.powers[pw_flashing]==2 p.powers[pw_flashing] = 0 end
					end
					P_DoPlayerPain(p)
					if (s.momz*P_MobjFlip(s) < 8*s.scale)
						s.momz = 8*s.scale*P_MobjFlip(s)
					end
				end
			end
			p.drawangle = PANG
			local SPEED = abs(s.momx)+abs(s.momy)
			if ((abs(s.momx)+abs(s.momy))  < 22*s.scale)
				P_InstaThrust(s, ANG, -18*s.scale)
			end
		end
		GS.cannotgrind = max(GS.cannotgrind or 0, 16) 
	end 
	return nil --
end

--When a player dies, then...?
RAIL.PlayerDeath = function(s, i, so, DMG, DMGTYPE) if not (s and s.valid) return end --
	local GS = s.GSgrind
	if GS and not ((GS.cannotgrind or 0)>18) and not (GS.leftrail)
		GS.cannotgrind = max(GS.cannotgrind or 0, 16)
		if GS.grinding
			RAIL.ExitRail(s)
			if not (s.skin and (string.gmatch(s.skin, "adventure")))
				s.momx,s.momy = 0,0
			end
			if not GS.leftrail GS.leftrail = 4 end	
		end
		GS.cannotgrind = max(GS.cannotgrind or 0, 20)
	end 
	return nil --
end

--Let these guys activate any assigned tag on death if the can't move flag is activated.
RAIL.JettDeath = function(s,i,so,DMG,DMGTYPE) if not (s and s.valid) return end --
	if (GS_RAILS!=nil) and s and s.valid and s.spawnpoint and s.spawnpoint.valid and s.spawnpoint.args 
	and (s.spawnpoint.args[0]==1)
		RAIL.Call_RailLineDef(s)
	end	
end